﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using OverStore.Runtime.PersistenceServiceProvider;
using OverStore.Runtime.ObjectRepository.Depot.IdentifierGenerator;
using OverStore.Runtime.ObjectRepository.ErrorTransformer;
using OverStore.Runtime.ObjectRepository.Reader;
using OverStore.Configuration.ObjectRepository;
using OverStore.Configuration.ObjectRepository.CallbackManager;
using OverStore.Runtime.ObjectRepository.CallbackManager;

namespace OverStore.Configuration.Builder.Fluent
{
    /// <summary>
    /// Configures object repository which maps class to table.
    /// </summary>
    /// <typeparam name="TInstance">The type of the instance.</typeparam>
    /// <typeparam name="TIdentifier">The type of the identifier.</typeparam>
    /// <typeparam name="TNext">The type of the next.</typeparam>
    public class TableMappingRepositoryStep<TInstance, TIdentifier, TNext>
        where TInstance : class
    {
        private readonly TableMappingRepositoryConfiguration<TInstance, TIdentifier> _Param;
        private readonly Func<TableMappingRepositoryConfiguration<TInstance, TIdentifier>, TNext> _OnCompleted;

        /// <summary>
        /// Initializes a new instance of the <see cref="TableMappingRepositoryStep&lt;TInstance, TIdentifier, TNext&gt;"/> class.
        /// </summary>
        /// <param name="parameters">The parameters.</param>
        /// <param name="onCompleted">The on completed.</param>
        public TableMappingRepositoryStep(
            TableMappingRepositoryConfiguration<TInstance, TIdentifier> parameters,
            Func<TableMappingRepositoryConfiguration<TInstance, TIdentifier>, TNext> onCompleted)
        {
            if (Object.ReferenceEquals(parameters, null))
                throw new ArgumentNullException("parameters");
            if (Object.ReferenceEquals(onCompleted, null))
                throw new ArgumentNullException("onCompleted");
            _Param = parameters;
            _OnCompleted = onCompleted;
        }

        /// <summary>
        /// Completes configuration.
        /// </summary>
        public TNext EndTableMapping()
        {
            return _OnCompleted(_Param);
        }

        /// <summary>
        /// Add primary key mapping.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="getValue">The get value.</param>
        /// <param name="setValue">The set value.</param>
        /// <param name="generator">The generator.</param>
        /// <returns></returns>
        public TableMappingRepositoryStep<TInstance, TIdentifier, TNext> MapKey(
            string name,
            Func<TInstance, TIdentifier> getValue,
            Action<TInstance, TIdentifier> setValue,
            IComponentConfiguration<IIdentifierGenerator<TInstance, TIdentifier>> generator)
        {
            _Param.MapKey(name, getValue, setValue, generator);
            return this;
        }

        /// <summary>
        /// Add mapping to primary key which values are generated by database.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="getValue">The get value.</param>
        /// <param name="setValue">The set value.</param>
        /// <param name="generator">The generator.</param>
        /// <returns></returns>
        public TableMappingRepositoryStep<TInstance, TIdentifier, TNext> MapDbGeneratedKey(
            string name,
            Func<TInstance, TIdentifier> getValue,
            Action<TInstance, TIdentifier> setValue,
            IComponentConfiguration<IIdentifierGenerator<TInstance, TIdentifier>> generator)
        {
            _Param.MapDbGeneratedKey(name, getValue, setValue, generator);
            return this;
        }

        /// <summary>
        /// Adds mapping to column stores scalar not-null value.
        /// </summary>
        public TableMappingRepositoryStep<TInstance, TIdentifier, TNext> MapScalarValue<TValue>(
            string columName,
            Func<TInstance, TValue> getValue,
            Action<TInstance, TValue> setValue,
            string errorMessage)
        {
            if (String.IsNullOrEmpty(columName))
                throw new ArgumentNullException("columName");
            if (Object.ReferenceEquals(getValue, null))
                throw new ArgumentNullException("getValue");
            if (Object.ReferenceEquals(setValue, null))
                throw new ArgumentNullException("setValue");
            _Param.MapScalarValue(columName, getValue, setValue, errorMessage);
            return this;
        }

        /// <summary>
        /// Adds mapping to column stores scalar not-null value.
        /// </summary>
        public TableMappingRepositoryStep<TInstance, TIdentifier, TNext> MapScalarValue<TValue>(
            string columName,
            Func<TInstance, TValue> getValue,
            Action<TInstance, TValue> setValue)
        {
            _Param.MapScalarValue(columName, getValue, setValue);
            return this;
        }

        /// <summary>
        /// Adds mapping to column stores scalar nullable value.
        /// </summary>
        public TableMappingRepositoryStep<TInstance, TIdentifier, TNext> MapNullableScalarValue<TValue>(
            string columName,
            Func<TInstance, TValue> getValue,
            Action<TInstance, TValue> setValue,
            string errorMessage)
        {
            if (String.IsNullOrEmpty(columName))
                throw new ArgumentNullException("columName");
            if (Object.ReferenceEquals(getValue, null))
                throw new ArgumentNullException("getValue");
            if (Object.ReferenceEquals(setValue, null))
                throw new ArgumentNullException("setValue");
            _Param.MapNullableScalarValue(columName, getValue, setValue, errorMessage);
            return this;
        }

        /// <summary>
        /// Adds mapping to column stores scalar nullable value.
        /// </summary>
        public TableMappingRepositoryStep<TInstance, TIdentifier, TNext> MapNullableScalarValue<TValue>(
            string columName,
            Func<TInstance, TValue> getValue,
            Action<TInstance, TValue> setValue)
        {
            _Param.MapNullableScalarValue(columName, getValue, setValue);
            return this;
        }

        /// <summary>
        /// Adds mapping to column stores reference to another instance of <typeparamref name="TReference"/> type with 
        /// <typeparamref name="TReferenceIdentifier"/> identifier.
        /// </summary>
        public TableMappingRepositoryStep<TInstance, TIdentifier, TNext> MapReference<TReference, TReferenceIdentifier>(
            string columnName,
            Func<TInstance, TReference> getValue,
            Action<TInstance, TReference> setValue,
            string errorMessage)
            where TReference : class
        {
            if (String.IsNullOrEmpty(columnName))
                throw new ArgumentNullException("columnName");
            if (Object.ReferenceEquals(getValue, null))
                throw new ArgumentNullException("getValue");
            if (Object.ReferenceEquals(setValue, null))
                throw new ArgumentNullException("setValue");
            _Param.MapReference<TReference, TReferenceIdentifier>(columnName, getValue, setValue, errorMessage);
            return this;
        }

        /// <summary>
        /// Adds mapping to column stores reference to another instance of <typeparamref name="TReference"/> type with 
        /// <typeparamref name="TReferenceIdentifier"/> identifier.
        /// </summary>
        public TableMappingRepositoryStep<TInstance, TIdentifier, TNext> MapReference<TReference, TReferenceIdentifier>(
            string columnName,
            Func<TInstance, TReference> getValue,
            Action<TInstance, TReference> setValue)
            where TReference : class
        {
            _Param.MapReference<TReference, TReferenceIdentifier>(columnName, getValue, setValue, null);
            return this;
        }

        /// <summary>
        /// Adds mapping to column stores reference to another instance of <typeparamref name="TReference"/> type with 
        /// <typeparamref name="TReferenceIdentifier"/> identifier or null.
        /// </summary>
        public TableMappingRepositoryStep<TInstance, TIdentifier, TNext> MapNullableReference<TReference, TReferenceIdentifier>(
            string columnName,
            Func<TInstance, TReference> getValue,
            Action<TInstance, TReference> setValue,
            string errorMessage)
            where TReference : class
        {
            if (String.IsNullOrEmpty(columnName))
                throw new ArgumentNullException("columnName");
            if (Object.ReferenceEquals(getValue, null))
                throw new ArgumentNullException("getValue");
            if (Object.ReferenceEquals(setValue, null))
                throw new ArgumentNullException("setValue");
            _Param.MapNullableReference<TReference, TReferenceIdentifier>(columnName, getValue, setValue, errorMessage);
            return this;
        }

        /// <summary>
        /// Adds mapping to column stores reference to another instance of <typeparamref name="TReference"/> type with 
        /// <typeparamref name="TReferenceIdentifier"/> identifier or null.
        /// </summary>
        public TableMappingRepositoryStep<TInstance, TIdentifier, TNext> MapNullableReference<TReference, TReferenceIdentifier>(
            string columnName,
            Func<TInstance, TReference> getValue,
            Action<TInstance, TReference> setValue)
            where TReference : class
        {
            _Param.MapNullableReference<TReference, TReferenceIdentifier>(columnName, getValue, setValue, null);
            return this;
        }


        /// <summary>
        /// Maps the aggregated collection.
        /// </summary>
        /// <typeparam name="TItem">The type of the item.</typeparam>
        /// <param name="getCollection">The delegate that gets collection from the instance.</param>
        /// <param name="restoreRelation">Delegate that restores reference to parent in aggregate instance.</param>
        /// <param name="useInstanceAsParameterForCollectionReloading">if set to <c>true</c> use instance as parameter for collection reloading.</param>
        /// <returns></returns>
        public TableMappingRepositoryStep<TInstance, TIdentifier, TNext> MapCollection<TItem>(
            Func<TInstance, IEnumerable<TItem>> getCollection,
            Action<TInstance, TItem, IPersistenceServiceProvider> restoreRelation,
            bool useInstanceAsParameterForCollectionReloading)
            where TItem : class
        {
            if (Object.ReferenceEquals(getCollection, null))
                throw new ArgumentNullException("getCollection");
            if (Object.ReferenceEquals(restoreRelation, null))
                throw new ArgumentNullException("restoreRelation");

            _Param.WithCallbackManager(new CollectionAggregateConfiguration<TInstance, TItem>
            {
                GetCollectionAggregate = getCollection,
                UseInstanceAsReloadParameter = useInstanceAsParameterForCollectionReloading,
                RestoreRelation = restoreRelation
            });
            return this;
        }

        /// <summary>
        /// Transforms the exception of specified type which message matches specified pattern to 
        /// exception with resulting message.
        /// </summary>
        public TableMappingRepositoryStep<TInstance, TIdentifier, TNext>
            TransformError<TException>(string pattern, MessageMatchType matchType, string resultingMessage)
            where TException : Exception
        {
            if (String.IsNullOrEmpty(resultingMessage))
                throw new ArgumentNullException("resultingMessage");
            _Param.TransformError<TException>(pattern, matchType, resultingMessage);
            return this;
        }

        /// <summary>
        /// Adds the callback to entity repository.
        /// </summary>
        public TableMappingRepositoryStep<TInstance, TIdentifier, TNext> AddCallback(Func<TInstance, PersistenceOperation, IPersistenceServiceProvider, bool> callback)
        {
            if (Object.ReferenceEquals(callback, null))
                throw new ArgumentNullException("callback");
            _Param.Callback(callback);
            return this;
        }

        /// <summary>
        /// Adds the callback to entity repository.
        /// </summary>
        public TableMappingRepositoryStep<TInstance, TIdentifier, TNext> AddCallback(Func<TInstance, PersistenceOperation, bool> callback)
        {
            if (Object.ReferenceEquals(callback, null))
                throw new ArgumentNullException("callback");
            _Param.Callback((entity, operation, provider) => callback(entity, operation));
            return this;
        }

        /// <summary>
        /// Configures the custom attacher for the instance. If you does not use this custom attacher,
        /// the repository will be configured with default by-identifier attacher.
        /// </summary>
        public AttacherBuilder<TInstance, TIdentifier, TableMappingRepositoryStep<TInstance, TIdentifier, TNext>>.IdentifierProvider
            ConfigureAttacher()
        {
            var p = new AttacherBuilder<TInstance, TIdentifier, TableMappingRepositoryStep<TInstance, TIdentifier, TNext>>.Parameters();
            p.OnCompleted = (a) =>
                {
                    _Param.WithAttacher(a);
                    return this;
                };
            return new AttacherBuilder<TInstance, TIdentifier, TableMappingRepositoryStep<TInstance, TIdentifier, TNext>>.IdentifierProvider(p);
        }

        /// <summary>
        /// Configures parameterless reader.
        /// </summary>
        public ParameterlessReaderBuilder<TInstance, TIdentifier, TableMappingRepositoryStep<TInstance, TIdentifier, TNext>>.SetCommandStep ParameterlessReader(bool useResultsetForObjectConstruction)
        {
            var p = new ParameterlessReaderBuilder<TInstance, TIdentifier, TableMappingRepositoryStep<TInstance, TIdentifier, TNext>>.Params(
                r =>
                {
                    _Param.ParameterlessReader = r;
                    return this;
                })
                {
                    UseResultsetForObjectConstruction = useResultsetForObjectConstruction
                };
            return new ParameterlessReaderBuilder<TInstance, TIdentifier, TableMappingRepositoryStep<TInstance, TIdentifier, TNext>>.SetCommandStep(p);
        }

        /// <summary>
        /// Configures parameterless reader.
        /// </summary>
        public ParameterizedReaderBuilder<TInstance, TIdentifier, TParameter, TableMappingRepositoryStep<TInstance, TIdentifier, TNext>>.SetCommandStep ParameterizedReader<TParameter>(bool useResultsetForObjectConstruction)
            where TParameter : class
        {
            var p = new ParameterizedReaderBuilder<TInstance, TIdentifier, TParameter, TableMappingRepositoryStep<TInstance, TIdentifier, TNext>>.Param(
                r =>
                {
                    _Param.ParameterizedReaders =
                        (_Param.ParameterizedReaders ?? Enumerable.Empty<IComponentConfiguration<IReader<TInstance, TIdentifier>>>())
                        .Concat(new IComponentConfiguration<IReader<TInstance, TIdentifier>>[] { r })
                        .ToList();
                    return this;
                })
            {
                UseResultsetForObjectConstruction = useResultsetForObjectConstruction
            };
            return new ParameterizedReaderBuilder<TInstance, TIdentifier, TParameter, TableMappingRepositoryStep<TInstance, TIdentifier, TNext>>.SetCommandStep(p);
        }
    }
}
